#pragma once

/* OpenCV Includes */
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include "BlobResult.h"

// Math define
#define _USE_MATH_DEFINES

/* C++ STL Includes */
#include <set>
#include <map>
#include <math.h>
#include <vector>
#include <sstream>
#include <assert.h>
#include <iostream>
#include <algorithm>
#include <functional>
#include <unordered_map>

/* Project Specific Includes */
#include "SeedlingSegment.h"
#include "ThinningAlgorithms.h"

// Minimum number of pixels for a terminal segment to be considered a root tip
#define Y_SCAN_STEP 10
#define MIN_BLOCK_SEP 5
#define MIN_ROOT_TIP_SIZE 10
#define X_RANGE_RELAXATION 5
#define Y_DISTANCE_RATIO 0.1
#define LOWER_X_DISTANCE_RATIO 0.70
#define UPPER_X_DISTANCE_RATIO 0.20

/* Type definitions for convenience */
typedef std::vector<CompleteSeedling*> CompleteSeedlings;
typedef std::tr1::unordered_multimap<std::string, SeedlingSegment*> SeedlingSegmentGraph;
typedef std::pair<SeedlingSegmentVector*, SeedlingSegmentGraph*> SegmentVectorGraphPair;
typedef std::pair<SeedlingSegmentGraph::iterator, SeedlingSegmentGraph::iterator> SeedlingSegmentEdges;


static bool sortByX(CBlob* a, CBlob* b) {
	return a->MinX() < b->MinX();
}

static bool sortByHeight(coordinate a, coordinate b) {
	return a.second > b.second;
}

static bool sortByMaxY(SeedlingSegment* a, SeedlingSegment* b) {
	int aY = a->getSegment().front().second > a->getSegment().back().second ? a->getSegment().front().second : a->getSegment().back().second, 
		bY = b->getSegment().front().second > b->getSegment().back().second ? b->getSegment().front().second : b->getSegment().back().second;
	return aY < bY;
}

static bool sortByMinY(SeedlingSegment* a, SeedlingSegment* b) {
	int aY = a->getSegment().front().second < a->getSegment().back().second ? a->getSegment().front().second : a->getSegment().back().second, 
		bY = b->getSegment().front().second < b->getSegment().back().second ? b->getSegment().front().second : b->getSegment().back().second;
	return aY < bY;
}

static bool sortBySize(SeedlingSegment* a, SeedlingSegment* b) {
	return a->getSegment().size() < b->getSegment().size();
}

class UniformityAnalysis
{
public:

	UniformityAnalysis(void);
	~UniformityAnalysis(void);
	IplImage* skeletonizeZS(IplImage *img);
	double processImage(IplImage *img, int numGroups, int type, int maxSeedlings);
	void testAdaptiveThreshold(int blockSize, int param1, int medianBlockSize, int thresh);
private:
	std::set<int> _groupKeys;
	double _millimetersPerPixel;
    unsigned int *_segmentIdImage;
	static const int fateTable[256];
	static const set<int> junctionSumSet;
	static const int validJunctionSums[24];
	static const CvScalar colors[20];
	static const int HEIGHT_THRESH = 40;
	static const int MIN_RATIO = 2;
	int _numGroups, _width, _height, _seedlingType, _seedlingsFound, 
        _maxNumSeedlings, _currentSegmentId, _singleChannelWidthStep;
	IplImage *_rChannel, *_gChannel, *_bChannel, *_seedlings;
	std::vector<CBlob*> _seedlingList;
	std::multimap<int,int> _groups;
    std::map<int, SeedlingSegment*> _segmentIdentifierMap;
	CvRect _seedlingBlobRect;
	bool *_visitedPixels;	
	std::stringstream _keyCreator;

	// Initializing and releasing memory used by the program
	void initialize(IplImage* img, int numGroups, int type, int maxSeedlings);
	void destroy(void);

	// Computing the conversion ratio (stage 1 of the program)
	void computeConversionRatio(void);

	// Extracting and processing seedlings to compute uniformity
	void extractSeedlings(void);
	double computeUniformity(void);
	int roundToNearest5MM(int length);
	void processBlobs(vector<CBlob*> &seedlings);
	
	// Marking and building seedling segments
	bool isJunctionPixel(coordinate center);
	bool isConnectedToJunction(coordinate center);
	coordinate findSeedlingStartPoint(CBlob *seedling);
	SegmentVectorGraphPair* findAllSegments(CBlob* blob);
	void buildSegment(SeedlingSegment* segment, int x, int y, coordinate *junction);

	// Second approach to seedling building
	std::string& buildSegmentKey(coordinate c);
	CompleteSeedlings* createSeedlings(SegmentVectorGraphPair *svg);
	SeedlingSegmentVector* findRootTips(SegmentVectorGraphPair *svg);
	void createGroup(SeedlingSegmentGraph *segmentGraph, CompleteSeedling* seedling);
	void addSegmentsToGroup(SeedlingSegmentGraph *segmentGraph, CompleteSeedling *seedling, std::string &mainSegmentKey, std::list<coordinate> &junctions);

	// Third approach to seedling building
	std::vector<range> numSeedlingsInBlob();
	CompleteSeedlings* createSeedlings(SegmentVectorGraphPair *svg, std::vector<range> segmentStartXRanges);
    void addSegmentsToIdentifierImage(SeedlingSegmentVector *segmentVector);
	CompleteSeedling* buildSeedling(SeedlingSegmentGraph *segmentGraph, SeedlingSegment *startSegment, SeedlingSegment *endSegment);
	void addNextLikelySegment(SeedlingSegmentGraph *segmentGraph, CompleteSeedling *seedling, SeedlingSegment *currentSegment, SeedlingSegment *endSegment);

	// Building seedlings
	int computeBlobWidthThreshold(std::vector<CBlob*> &blobs);
	void pruneShortTerminalEdges(SeedlingSegmentGraph *segmentGraph);
	void correctSeedlingEndpoints(SeedlingSegmentVector *segmentVector);
	double measureAngle(SeedlingSegment *main, SeedlingSegment *candidate);
	bool segmentIncreasesLength(SeedlingSegment *first, SeedlingSegment *second);
	bool isPartOfExistingSeedlings(SeedlingSegment *segment, CompleteSeedlings *seedlings);
	CompleteSeedlings* createSeedlingsFromSegments(SegmentVectorGraphPair *separatedSegments);
	SeedlingSegmentVector* potentialSecondarySegments(SegmentVectorGraphPair *segmentVectorGraph);
	CompleteSeedling* buildSeedling(SeedlingSegmentGraph *segmentGraph, SeedlingSegment *startSegment);
	SeedlingSegment* selectBestSegment(CompleteSeedling *seedling, std::vector<SeedlingSegment*> &choices);
	bool graphContainsSegment(SeedlingSegmentGraph *segmentGraph, std::string junctionKey, SeedlingSegment *seedling);
	bool isConnectingSegment(SeedlingSegmentGraph *segmentGraph, SeedlingSegment *testSegment, std::string &mainSegmentKey);
	void checkConnectedSegments(SeedlingSegmentGraph *segmentGraph, CompleteSeedling *seedling, std::string &mainSegmentKey);

	// Initializing, marking, and checking visited pixels array
	void initVisitedPixelsArray(CBlob* blob);
	void setVisited(int x, int y);
	bool isNotVisited(int x, int y);

	// Miscellaneous operations
	void addToImage(std::vector<SeedlingSegment*> *seedlingSegments, IplImage *segmentsImage);
	void addSeedlingsToImage(CompleteSeedlings *completeSeedlings, IplImage *completeSeedlingImage);
	void getNeighbors(int x, int y, std::vector<coordinate> &neighbors);
	void printSeedlingInfo(CompleteSeedlings *seedlings);
	void invertImage(IplImage *src, IplImage *dst);
	void bitwiseAnd(IplImage *src1, IplImage *src2, IplImage *dst);
	void setPixels(IplImage *img, std::vector<coordinate> &pixels, CvScalar color);
	void thresholdRange(IplImage *src, IplImage *dst, 
		int lowerThresh, int upperThresh, 
		int maxValue, bool keepSrcValue);
};
